home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
45975
/
45975.xpi
/
content
/
clicknlearn.js
next >
Wrap
Text File
|
2009-11-23
|
19KB
|
461 lines
/*
* Copyright (c) 2009 Bui Viet Thanh (thanhbv@gmail.com).
*
* This file is part of clicknlearn.
*
* clicknlearn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* clicknlearn is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with clicknlearn. If not, see <http://www.gnu.org/licenses/>.
*/
Components.utils.import("resource://clicknlearn/cnldao.js");
function Clicknlearn() {
window.addEventListener("click", this.eventClick, false);
window.addEventListener("load", this.eventLoad, false);
window.addEventListener("contextmenu", this.eventContextMenu, true);
window.addEventListener("keypress", this.eventKeyPress, false);
this.modifier = 1;//shiftKey + 2 * ctrlKey + 4 * altKey
this.maxWidth = 640;
//static:
this.BOUNDARY_NODES = ['BODY', 'BR', 'CAPTION', 'CENTER', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'INPUT', 'LI', 'OL', 'P', 'PRE', 'SCRIPT', 'SELECT', 'TD', 'TEXTAREA', 'TR', 'UL'];
}
Clicknlearn.prototype.eventLoad = function(event) { CNL.eventLoadImpl(event); }
Clicknlearn.prototype.eventLoadImpl = function(event) {
this.currentDoc = null;
this.eventClientX = null;
this.eventClientY = null;
//the following statement must place here (not in clicknlearn.xul)
// because if not => document.getElementById("string-bundle") will == null !
this.stringsBundle = document.getElementById("string-bundle");
//Oh! see https://wiki.mozilla.org/Labs/JS_Modules#StringBundle
//we can initialized stringsBundle by the following statement:
// this.stringsBundle = new StringBundle("chrome://clicknlearn/locale/string-bundle.properties");
//Oh! :D https://wiki.mozilla.org/Labs/JS_Modules is still LAB!
//& be implemented in Weave: resource://weave/ext/[StringBundle | Observers | Preferences].js,
this.DICLOOKUP = new DicLookup();
this.REMIND = new Remind();
this.clickIdHash = {"requestWordFromOtherSourcesButton": [DicLookup.prototype.requestWordFromOtherSources, this.DICLOOKUP]
, "remindButton": [Remind.prototype.remindCurrentWord, this.REMIND]
, "removeWordButton": [Remind.prototype.removeCurrentWord, this.REMIND]
, "cnl_settingsButton": [this.showPrefsWindow, this]};
var appcontent = document.getElementById("appcontent");
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
}
Clicknlearn.prototype.showPrefsWindow = function(){
window.open("chrome://clicknlearn/content/options.xul", "optionsWindow", "chrome,centerscreen");
}
/**
* do something with the loaded page.
*/
Clicknlearn.prototype.onPageLoad = function(event){
CNL.currentDoc = event.originalTarget; // doc is document that triggered "onload" event
CNL.attachCss();
CNL.REMIND.highlightWords();
}
/**
* To fix issue 3:
* [linux only] shift + right-click on a word still open the context menu
*/
Clicknlearn.prototype.eventContextMenu = function(event) { CNL.eventContextMenuImpl(event); }
Clicknlearn.prototype.eventContextMenuImpl = function(event) {
// Return if the click was not on an HTML document.
if (event.target.ownerDocument != "[object XPCNativeWrapper [object HTMLDocument]]")
return;
// Check if the correct mouse button and the correct modifier were pressed. If so, look the word up.
// & prevent the context menu from opening
if (event.button == 2 && this.modifierIsPressed(event, this.modifier))
event.preventDefault();
}
/**
* remove lookup div when user press ESC key
*/
Clicknlearn.prototype.eventKeyPress = function(event) { CNL.eventKeyPressImpl(event); }
Clicknlearn.prototype.eventKeyPressImpl = function(event) {
if(this.DICLOOKUP.div == null)
return;
// alert(event.keyCode + ", " + KeyEvent.DOM_VK_ENTER); ??? 13 & 14
if(event.keyCode == KeyEvent.DOM_VK_ESCAPE)
this.DICLOOKUP.clearAll();
else if(event.keyCode == 13){
var element = event.target;
// alert(element);
if(element.id == 'cnlwordbox' && element.value){
this.DICLOOKUP.currentWord = element.value;
this.DICLOOKUP.formatCurrentWord();
this.DICLOOKUP.reset();
this.DICLOOKUP.div = this.attachDiv(this.DICLOOKUP.divId);
this.DICLOOKUP.printLookingUp();
this.DICLOOKUP.requestWord();
}
}
}
Clicknlearn.prototype.eventClick = function(event) { CNL.eventClickImpl(event); }
Clicknlearn.prototype.eventClickImpl = function(event) {
// Return if the click was not on an HTML document.
if (event.target.ownerDocument != "[object XPCNativeWrapper [object HTMLDocument]]")
return;
// Check if the correct mouse button and the correct modifier were pressed. If so, look the word up.
if (event.button == 2 && this.modifierIsPressed(event, this.modifier))
this.lookUpWord(event);
// If it's a left-click, hide the lookup div
if (event.button == 0 && this.DICLOOKUP.div != null) {
var element = event.target;
var clickOnElementFunc = this.clickIdHash[element.id];
if(clickOnElementFunc != undefined){
clickOnElementFunc[0].apply(clickOnElementFunc[1]);
return;
}
var clickTarget = "";
while (element != null) {
if(element.id == this.DICLOOKUP.divId){
clickTarget = this.DICLOOKUP.divId;
break;
}
else
element = element.parentNode;
}
if (clickTarget != this.DICLOOKUP.divId)
this.DICLOOKUP.clearAll();
}
}
Clicknlearn.prototype.modifierIsPressed = function(event, modifier) {
return event.shiftKey + 2 * event.ctrlKey + 4 * event.altKey == modifier;
}
/**
* get current word, and delegate the work looking up the definition from internet to a DicLookup object
* @param event click event use to get the word and sentence that the user click on
*/
Clicknlearn.prototype.lookUpWord = function(event) {
this.getCurrentWordAndSentence(event.rangeParent, event.rangeOffset);
this.currentDoc = event.target.ownerDocument;
this.eventClientX = event.clientX;
this.eventClientY = event.clientY;
this.DICLOOKUP.reset();
this.DICLOOKUP.div = this.attachDiv(this.DICLOOKUP.divId);
if (! this.DICLOOKUP.currentWord){
this.DICLOOKUP.showDivWithEmptyWord();
} else {
this.DICLOOKUP.printLookingUp();
this.DICLOOKUP.requestWord();
}
}
Clicknlearn.prototype.removeDiv = function(div) {
if (this.currentDoc != null && div != null && div.parentNode != null)
div.parentNode.removeChild(div);
}
Clicknlearn.prototype.attachDiv = function(divId) {
this.attachCss();
var div = this.currentDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
div.id = divId;
div.className = "clicknlearn";
div.style.maxWidth = this.maxWidth + "px";
this.setDivPosition(div);
return this.currentDoc.body.appendChild(div);
}
Clicknlearn.prototype.attachCss = function() {
var cssAlreadyInPlace = this.currentDoc.getElementById("clicknlearnCSS");
if (cssAlreadyInPlace == null) {
var heads = this.currentDoc.getElementsByTagName("head");
var link = this.currentDoc.createElement("link");
link.rel = "stylesheet";
link.id = "clicknlearnCSS";
link.type = "text/css";
link.href = "chrome://Clicknlearn/content/css.css";
heads[0].appendChild(link);
}
}
Clicknlearn.prototype.setDivPosition = function(div) {
// Set horizontal position.
if (this.eventClientX + this.maxWidth + 64 > this.currentDoc.width)
if (this.currentDoc.width > this.maxWidth)
div.style.left = this.currentDoc.width - this.maxWidth - 64 + "px";
else
div.style.left = "0px";
else
div.style.left = this.eventClientX + "px";
// Set vertical position.
div.style.top = this.eventClientY + this.currentDoc.documentElement.scrollTop + this.currentDoc.body.scrollTop + 10 + "px";
}
/**
* Set [this.DICLOOKUP.currentWord, this.DICLOOKUP.currentRemind] = the [word, sentence] that user click on
* or ["", xxx] if not found
* @param parent The DOM node that user click on
* @param offset pos in the parent node that user click on
*/
Clicknlearn.prototype.getCurrentWordAndSentence = function(parent, offset) {
// Return the selected text if it was clicked on.
var selection = document.commandDispatcher.focusedWindow.getSelection();
this.DICLOOKUP.currentWord = "";
if (selection != "") {
var mouseIsOverSelection = ! selection.isCollapsed && selection.getRangeAt(0).isPointInRange(parent, offset);
if (mouseIsOverSelection){
this.DICLOOKUP.currentWord = selection.toString();
this.DICLOOKUP.formatCurrentWord();
}
}
var textNodeLists;
if(! this.DICLOOKUP.currentWord){
textNodeLists = this.expandTextNodeLists(parent);
//TODO haven't been tested
if(textNodeLists[0].length == 0)
return;
this.DICLOOKUP.currentWord = this.findWord(textNodeLists[0], textNodeLists[1], offset);
this.DICLOOKUP.formatCurrentWord();
}
if(this.DICLOOKUP.currentWord){
let wordData = CNL_DAO.getWordData(this.DICLOOKUP.currentWord);
if(wordData && wordData.remind){
this.DICLOOKUP.currentRemind = wordData.remind;
// this.DICLOOKUP.currentUrl = wordData.originalUrl;
}
else{
if(! textNodeLists)
textNodeLists = this.expandTextNodeLists(parent);
this.DICLOOKUP.currentRemind = this.findSentence(textNodeLists[0], textNodeLists[1], offset);
// this.DICLOOKUP.currentUrl = this.currentDoc.location.toString();
}
if(wordData == null)
this.DICLOOKUP.remember = false;
else
this.DICLOOKUP.remember = wordData.read == 0;
}
}
/**
* return [prevTextNodeList, nextTextNodeList]
*/
Clicknlearn.prototype.expandTextNodeLists = function(node){
// alert("click on: " + node.textContent);
////////////go up to a tag that make a new line
var startNode = node;
var iterateToEndTag = false;
var prevTextNodeList = new Array();
while(this.isNotBoundaryNode(startNode.nodeName)){
if(startNode.nodeType == Node.TEXT_NODE && startNode.textContent)
prevTextNodeList[prevTextNodeList.length] = startNode;
if(!iterateToEndTag){
if(startNode.previousSibling){
startNode = startNode.previousSibling;
iterateToEndTag = true;
}
else
startNode = startNode.parentNode;
}
else{
if(startNode.lastChild)//not TextNode
startNode = startNode.lastChild;
else if(startNode.previousSibling)
startNode = startNode.previousSibling;
else{
startNode = startNode.parentNode;
iterateToEndTag = false;
}
}
}
// alert("start break line tag: " + startNode.tagName + iterateToEndTag);
////////////go down to a tag that make a new line
var endNode = node;
var iterateToStartTag = true;
var nextTextNodeList = new Array();
while(this.isNotBoundaryNode(endNode.nodeName)){
if(endNode.nodeType == Node.TEXT_NODE && endNode.textContent)
nextTextNodeList[nextTextNodeList.length] = endNode;
if(iterateToStartTag){
if(endNode.firstChild)
endNode = endNode.firstChild;
else if(endNode.nextSibling)
endNode = endNode.nextSibling;
else{
endNode = endNode.parentNode
iterateToStartTag = false;
}
}
else{
if(endNode.nextSibling){
endNode = endNode.nextSibling;
iterateToStartTag = true;
}
else
endNode = endNode.parentNode;
}
}
// alert("End break line tag: " + endNode.tagName + iterateToStartTag);
return [prevTextNodeList, nextTextNodeList];
}
/**
* @param boundaryDelimiters
* @param prevTextNodeList find from prevTextNodeList[0][offset] backward
* @param nextTextNodeList find from nextTextNodeList[0][offset] forward
* @param offset offset of the position we want to start finding.
* It is the offset in prevTextNodeList[0] (== nextTextNodeList[0])
* @return text, or "" if not found
*/
Clicknlearn.prototype.findText = function(boundaryDelimiters, prevTextNodeList, nextTextNodeList, offset){
var parent = prevTextNodeList[0];
var startOffset = -1, endOffset = -1;//startOffset == -1 mean rangeStartIsNotSet
var foundPos, startNodeIndex, endNodeIndex;
//prevTextNodeList
foundPos = this.findCharListPositionInTextNodeList(boundaryDelimiters /*" \t\n"*/, prevTextNodeList, offset, true);
startNodeIndex = foundPos[0];
startOffset = foundPos[1];
if(startOffset == -1){
startNodeIndex = prevTextNodeList.length - 1;
startOffset = 0;
}
//nextTextNodeList
foundPos = this.findCharListPositionInTextNodeList(boundaryDelimiters, nextTextNodeList, offset, false);
endNodeIndex = foundPos[0];
endOffset = foundPos[1];
// //debug
// var log = "[";
// for(var i=0; i< nextTextNodeList.length; i++)
// log += nextTextNodeList[i].textContent + "]["
// log += "\n\n" + endNodeIndex + ", " + endOffset;
// alert(log);
// //~debug
if(endOffset == -1){
endNodeIndex = nextTextNodeList.length - 1;
endOffset = nextTextNodeList[endNodeIndex].textContent.length - 1;
}
var range = parent.ownerDocument.createRange();
range.setStart(prevTextNodeList[startNodeIndex], startOffset);
range.setEnd(nextTextNodeList[endNodeIndex], endOffset + 1);
//see http://stackoverflow.com/questions/1586231/strange-behaviour-with-range-tostring
var sel = content.getSelection(); //use HTML window insteads of XUL window
var prevSelectedRange;
//TODO need review. we check because sometimes sel.getRangeAt(0) will raise a exception
if(sel.rangeCount > 0){
prevSelectedRange = sel.getRangeAt(0);
sel.removeAllRanges();
}
sel.addRange(range);
var ret = sel.toString()
sel.removeAllRanges();
if (prevSelectedRange != undefined)
sel.addRange(prevSelectedRange);
return ret;
}
/**
* When find sentence, we must care if the pos that user click on (that we start finding) is in a PRE tag
*/
Clicknlearn.prototype.findSentence = function(prevTextNodeList, nextTextNodeList, offset){
var delimiters = ".?!;|";
var s = this.isInPreTag(parent)?
this.findText("\n" + delimiters, prevTextNodeList, nextTextNodeList, offset) :
this.findText(delimiters, prevTextNodeList, nextTextNodeList, offset);
return CNLUtils.trim(s, "\n " + delimiters);
}
/**
* When find word, we don't care if the pos that user click on (that we start finding) is in a PRE tag
*/
Clicknlearn.prototype.findWord = function(prevTextNodeList, nextTextNodeList, offset){
return this.findText(" \t\n./", prevTextNodeList, nextTextNodeList, offset);
}
/**
* return [foundNodeIndex, foundOffset]
* or [xxx, -1] if not found
* @param charList the chatList to find
* @param textNodeList text node list to find in
* @param offsetInFirstNode find from pos textNodeList[0][offsetInFirstNode]
* @param findBackward true if find backward
*/
Clicknlearn.prototype.findCharListPositionInTextNodeList = function(charList, textNodeList, offsetInFirstNode, findBackward){
var foundNodeIndex = textNodeList.length - 1, offset, i, j, foundOffset;
if(findBackward){
foundOffset = -1;
for(j=0; j<charList.length; j++){
offset = textNodeList[0].textContent.lastIndexOf(charList[j], offsetInFirstNode);
if(offset != -1){
if(foundNodeIndex > 0 || foundNodeIndex == 0 && foundOffset < offset){
foundNodeIndex = 0;
foundOffset = offset;
}
}
else
for(i=1; i <= foundNodeIndex; i++){
offset = textNodeList[i].textContent.lastIndexOf(charList[j]);
if(offset != -1){
if(foundNodeIndex > i || foundNodeIndex == i && foundOffset < offset){
foundNodeIndex = i;
foundOffset = offset;
break;
}
}
}
}
}
else{
foundOffset = 1000;
for(j=0; j<charList.length; j++){
offset = textNodeList[0].textContent.indexOf(charList[j], offsetInFirstNode);
if(offset != -1){
if(foundNodeIndex > 0 || foundNodeIndex == 0 && foundOffset > offset){
foundNodeIndex = 0;
foundOffset = offset;
}
}
else
for(i=1; i <= foundNodeIndex; i++){
offset = textNodeList[i].textContent.indexOf(charList[j]);
if(offset != -1){
// alert("find backward: "+ foundNodeIndex + ", " + i + ", " + foundOffset + ", " + offset);
if(foundNodeIndex > i || foundNodeIndex == i && foundOffset > offset){
foundNodeIndex = i;
foundOffset = offset;
break;
}
}
}
}
if(foundOffset == 1000)
foundOffset = -1;
}
return [foundNodeIndex, foundOffset];
}
Clicknlearn.prototype.isInPreTag = function(node){
while(node.parentNode){
node = node.parentNode;
if(node.tagName == "PRE")
return true;
}
return false;
}
/**
* returns true if the passed node name is not a "boundary" node
*/
Clicknlearn.prototype.isNotBoundaryNode = function(nodeName) {
return CNLUtils.search(this.BOUNDARY_NODES, nodeName) == -1;
}